package game;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.*;

public class ChineseChessGame extends ChineseChessBasedGame {

	private final Map<Point, ChChess> chessMap = new HashMap<Point, ChChess>();
	private final Color lineColor = Color.black;
	private final Color boardColor = Color.yellow;
	private final Color player0Color = Color.black;
	private final Color player1Color = Color.red;
	private Point king0Location;
	private Point king1Location;

	public ChineseChessGame() {
		super();
	}

	@Override
	public void Init() {
		clearChessMap();
		// chess of player 0(block side)
		listener.OnPlayerColorChange(0, Color.black);
		king0Location = new Point(4, 0);
		chessMap.put(new Point(0, 0), new ChChariotChess(0, this));
		chessMap.put(new Point(1, 0), new ChHorseChess(0, this));
		chessMap.put(new Point(2, 0), new ChElephantChess(0, this));
		chessMap.put(new Point(3, 0), new ChAdvisorChess(0, this));
		chessMap.put(king0Location, new ChGeneralChess(0, this));
		chessMap.put(new Point(5, 0), new ChAdvisorChess(0, this));
		chessMap.put(new Point(6, 0), new ChElephantChess(0, this));
		chessMap.put(new Point(7, 0), new ChHorseChess(0, this));
		chessMap.put(new Point(8, 0), new ChChariotChess(0, this));
		chessMap.put(new Point(1, 2), new ChCannonChess(0, this));
		chessMap.put(new Point(7, 2), new ChCannonChess(0, this));
		chessMap.put(new Point(0, 3), new ChSoldierChess(0, this));
		chessMap.put(new Point(2, 3), new ChSoldierChess(0, this));
		chessMap.put(new Point(4, 3), new ChSoldierChess(0, this));
		chessMap.put(new Point(6, 3), new ChSoldierChess(0, this));
		chessMap.put(new Point(8, 3), new ChSoldierChess(0, this));
		// chess of player 1(set side)
		listener.OnPlayerColorChange(1, Color.red);
		king1Location = new Point(4, 9);
		chessMap.put(new Point(0, 9), new ChChariotChess(1, this));
		chessMap.put(new Point(1, 9), new ChHorseChess(1, this));
		chessMap.put(new Point(2, 9), new ChElephantChess(1, this));
		chessMap.put(new Point(3, 9), new ChAdvisorChess(1, this));
		chessMap.put(king1Location, new ChGeneralChess(1, this));
		chessMap.put(new Point(5, 9), new ChAdvisorChess(1, this));
		chessMap.put(new Point(6, 9), new ChElephantChess(1, this));
		chessMap.put(new Point(7, 9), new ChHorseChess(1, this));
		chessMap.put(new Point(8, 9), new ChChariotChess(1, this));
		chessMap.put(new Point(1, 7), new ChCannonChess(1, this));
		chessMap.put(new Point(7, 7), new ChCannonChess(1, this));
		chessMap.put(new Point(0, 6), new ChSoldierChess(1, this));
		chessMap.put(new Point(2, 6), new ChSoldierChess(1, this));
		chessMap.put(new Point(4, 6), new ChSoldierChess(1, this));
		chessMap.put(new Point(6, 6), new ChSoldierChess(1, this));
		chessMap.put(new Point(8, 6), new ChSoldierChess(1, this));

	}

	@Override
	public Point GetBoardSize() {
		return new Point(9, 10);
	}

	@Override
	public boolean IsActionableWithoutTarget(Point source) {
		if (HasChess(source)
				&& chessMap.get(source).owner == GetCurrentPlayer())
			return false;
		else
			return true;
	}

	@Override
	public void Action(Point source) {
		// it must select a empty location or other player's chess so this
		// method called
		// Therefore this method should do nothing
	}

	@Override
	public void Action(Point source, Point target) {
		assert chessMap.get(source).getOwner() == GetCurrentPlayer();
		if (HasChess(source) && GetValidTargets(source).contains(target)) {
			if (WillGameOver(target)) {
				listener.OnGameOver();
			} else {
				chessMap.put(target, chessMap.get(source));
				chessMap.remove(source);
				TryUpdateKingsLocation(target);
				NextPlayer();
			}
		}
	}

	@Override
	public void PaintLocation(Graphics g, Point size, Point location) {
		PaintGridLine(g, size, location);
		if (HasChess(location))
			chessMap.get(location).Paint(g, size);
	}

	@Override
	public Set<Point> GetValidTargets(Point source) {
		if (HasChess(source))
			return chessMap.get(source).GetValidTarget(source);
		else
			return new HashSet<Point>();
	}

	public Color getLineColor() {
		return lineColor;
	}

	public Color getBoardColor() {
		return boardColor;
	}

	public Color getPlayerColor(int playerID) {
		if (playerID == 0)
			return player0Color;
		else
			return player1Color;
	}

	boolean HasChess(Point location) {
		return chessMap.containsKey(location);
	}

	int GetOwner(Point location) {
		if (HasChess(location))
			return chessMap.get(location).owner;
		return -1;
	}

	boolean InPalace(Point location) {
		return InRange(location) && location.x >= 3 && location.x < 6
				&& (location.y < 3 || location.y >= 7);
	}

	Point GetKingLocation(int owner) {
		if (owner == 0)
			return king0Location;
		else
			return king1Location;
	}

	Class<? extends ChChess> getChessType(Point location) {
		return chessMap.get(location).getClass();
	}

	private boolean WillGameOver(Point finalTarget) {
		if (chessMap.get(finalTarget) instanceof ChGeneralChess) {
			return true;
		} else {
			return false;
		}
	}

	private void TryUpdateKingsLocation(Point location) {
		if (chessMap.get(location) instanceof ChGeneralChess) {
			if (chessMap.get(location).owner == 0)
				king0Location = location;
			else
				king1Location = location;
		}
	}

	private void PaintGridLine(Graphics g, Point size, Point location) {
		g.setColor(boardColor);
		g.fillRect(0, 0, size.x, size.y);
		g.setColor(lineColor);
		g.drawLine((int) (size.getX() / 2), 0, (int) (size.getX() / 2),
				(int) size.getY());
		g.drawLine(0, (int) (size.getY() / 2), (int) size.getX(),
				(int) (size.getY() / 2));
		g.setColor(boardColor);
		int x = location.x, y = location.y;
		if (x == 0) {
			g.drawLine(0, (int) (size.getY() / 2), (int) (size.getX() / 2),
					(int) (size.getY() / 2));
		}
		if (y == 0 || ((x != 0 && x != 8) && y == 5)) {
			g.drawLine((int) (size.getX() / 2), 0, (int) (size.getX() / 2),
					(int) (size.getY() / 2));
		}
		if (x == 8) {
			g.drawLine((int) ((size.getX() + 1) / 2), (int) (size.getY() / 2),
					(int) (size.getX()), (int) (size.getY() / 2));
		}
		if (y == 9 || ((x != 0 && x != 8) && y == 4)) {
			g.drawLine((int) (size.getX() / 2), (int) ((size.getY() + 1) / 2),
					(int) (size.getX() / 2), (int) (size.getY()));
		}
		PaintSlash(g, size, location);
	}

	private void PaintSlash(Graphics g, Point size, Point location) {
		int x = location.x, y = location.y;
		if (x >= 3 && x < 6 && (y < 3 || y >= 7)) {
			if (x != 5 || y != 9) {
				g.drawLine((int) (size.getX() / 2), (int) (size.getY() / 2),
						size.x, size.y);
			}
			if (x != 5 || y != 0) {
				g.drawLine((int) (size.getX() / 2), (int) (size.getY() / 2),
						size.x, 0);
			}
			if (x != 3 || y != 9) {
				g.drawLine((int) (size.getX() / 2), (int) (size.getY() / 2), 0,
						size.y);
			}
			if (x != 3 || y != 0) {
				g.drawLine((int) (size.getX() / 2), (int) (size.getY() / 2), 0,
						0);
			}
		}
	}

	private void clearChessMap() {
		assert chessMap != null;
		for (Map.Entry<Point, ChChess> e : chessMap.entrySet()) {
			e.getValue().dispose();
		}
		chessMap.clear();
	}

}

abstract class ChChess extends ChineseChess {
	private final Color chessBackGroundColor = Color.white;

	protected ChChess(int owner, ChineseChessGame game) {
		super(owner, game);
	}

	public int getOwner() {
		return owner;
	}

	public abstract Set<Point> GetValidTarget(Point source);

	protected boolean isPlaceableTarget(Point target) {
		if (!getGame().InRange(target))
			return false;
		if (!getGame().HasChess(target))
			return true;
		if (getGame().GetOwner(target) != owner)
			return true;
		return false;
	}

	protected Point nextStep(Point start, Point direction) {
		return new Point(start.x + direction.x, start.y + direction.y);
	}

	protected Point[] orthogonalVectors(Point direction) {
		return new Point[] { new Point(direction.y * -1, direction.x),
				new Point(direction.y, direction.x * -1) };
	}

	protected boolean isHorOrVerUnitVector(Point direction) {
		return (direction.x == 0 || direction.y == 0)
				&& (int) direction.distance(0, 0) == 1;
	}
}

class ChChariotChess extends ChChess {
	protected ChChariotChess(int owner, ChineseChessGame game) {
		super(owner, game);
	}

	@Override
	public String getName() {
		if (owner == 0)
			return "車";
		else
			return "俥";
	}

	@Override
	public Set<Point> GetValidTarget(Point source) {
		Set<Point> validTargets = new HashSet<Point>();
		addValidTarget(validTargets, nextStep(source, new Point(0, 1)),
				new Point(0, 1));
		addValidTarget(validTargets, nextStep(source, new Point(0, -1)),
				new Point(0, -1));
		addValidTarget(validTargets, nextStep(source, new Point(1, 0)),
				new Point(1, 0));
		addValidTarget(validTargets, nextStep(source, new Point(-1, 0)),
				new Point(-1, 0));
		validTargets.remove(source);
		return validTargets;
	}

	private void addValidTarget(Set<Point> targets, Point testPoint,
			Point direction) {
		if (isPlaceableTarget(testPoint)) {
			targets.add(testPoint);
			if (!getGame().HasChess(testPoint))
				addValidTarget(targets, nextStep(testPoint, direction),
						direction);
		}
	}
}

class ChHorseChess extends ChChess {
	protected ChHorseChess(int owner, ChineseChessGame game) {
		super(owner, game);
	}

	@Override
	public String getName() {
		if (owner == 0)
			return "馬";
		else
			return "傌";
	}

	@Override
	public Set<Point> GetValidTarget(Point source) {
		Set<Point> validTargets = new HashSet<Point>();
		addValidTarget(validTargets, source, new Point(0, 1));
		addValidTarget(validTargets, source, new Point(0, -1));
		addValidTarget(validTargets, source, new Point(1, 0));
		addValidTarget(validTargets, source, new Point(-1, 0));
		return validTargets;
	}

	private void addValidTarget(Set<Point> targets, Point testPoint,
			Point direction) {
		assert isHorOrVerUnitVector(direction);
		Point firstStep = nextStep(testPoint, direction);
		if (!isHobbling(firstStep)) {
			Point secondStep = nextStep(firstStep, direction);
			for (Point nextDirection : orthogonalVectors(direction)) {
				Point thirdStep = nextStep(secondStep, nextDirection);
				if (isPlaceableTarget(thirdStep))
					targets.add(thirdStep);
			}
		}
	}

	private boolean isHobbling(Point testPoint) {
		return !getGame().HasChess(testPoint) && isPlaceableTarget(testPoint);
	}
}

class ChElephantChess extends ChChess {
	protected ChElephantChess(int owner, ChineseChessGame game) {
		super(owner, game);
	}

	@Override
	public String getName() {
		if (owner == 0)
			return "象";
		else
			return "相";
	}

	@Override
	public Set<Point> GetValidTarget(Point source) {
		Set<Point> validTargets = new HashSet<Point>();
		addValidTarget(validTargets, source, new Point(0, 1));
		addValidTarget(validTargets, source, new Point(0, -1));
		addValidTarget(validTargets, source, new Point(1, 0));
		addValidTarget(validTargets, source, new Point(-1, 0));
		return validTargets;
	}

	private void addValidTarget(Set<Point> targets, Point testPoint,
			Point direction) {
		assert isHorOrVerUnitVector(direction);
		Point firstStep = nextStep(testPoint, direction);
		for (Point nextDirection : orthogonalVectors(direction)) {
			Point secondStep = nextStep(firstStep, nextDirection);
			if (!getGame().HasChess(secondStep)) {
				Point thirdStep = nextStep(secondStep,
						nextStep(direction, nextDirection));
				if (isPlaceableTarget(thirdStep)) {
					targets.add(thirdStep);
				}
			}
		}
	}
}

class ChAdvisorChess extends ChChess {
	protected ChAdvisorChess(int owner, ChineseChessGame game) {
		super(owner, game);
	}

	@Override
	public String getName() {
		if (owner == 0)
			return "士";
		else
			return "仕";
	}

	@Override
	public Set<Point> GetValidTarget(Point source) {
		Set<Point> validTargets = new HashSet<Point>();
		addValidTarget(validTargets, source, new Point(0, 1));
		addValidTarget(validTargets, source, new Point(0, -1));
		addValidTarget(validTargets, source, new Point(1, 0));
		addValidTarget(validTargets, source, new Point(-1, 0));
		return validTargets;
	}

	private void addValidTarget(Set<Point> targets, Point testPoint,
			Point direction) {
		assert isHorOrVerUnitVector(direction);
		Point firstStep = nextStep(testPoint, direction);
		for (Point nextDirection : orthogonalVectors(direction)) {
			Point secondStep = nextStep(firstStep, nextDirection);
			if (((ChineseChessGame) getGame()).InPalace(secondStep)
					&& isPlaceableTarget(secondStep)) {
				targets.add(secondStep);
			}
		}
	}
}

class ChGeneralChess extends ChChess {
	protected ChGeneralChess(int owner, ChineseChessGame game) {
		super(owner, game);
	}

	@Override
	public String getName() {
		if (owner == 0)
			return "將";
		else
			return "帥";
	}

	@Override
	public Set<Point> GetValidTarget(Point source) {
		Set<Point> validTargets = new HashSet<Point>();
		addValidTarget(validTargets, source, new Point(0, 1));
		addValidTarget(validTargets, source, new Point(0, -1));
		addValidTarget(validTargets, source, new Point(1, 0));
		addValidTarget(validTargets, source, new Point(-1, 0));
		if (canFly()) {
			validTargets.add(((ChineseChessGame) getGame())
					.GetKingLocation(1 - owner));
		}
		return validTargets;
	}

	private void addValidTarget(Set<Point> targets, Point testPoint,
			Point direction) {
		assert isHorOrVerUnitVector(direction);
		Point firstStep = nextStep(testPoint, direction);
		if (((ChineseChessGame) getGame()).InPalace(firstStep)
				&& isPlaceableTarget(firstStep)
				&& !(getGame().HasChess(firstStep) && getGame().getChessType(
						firstStep) == ChSoldierChess.class)) {
			targets.add(firstStep);
		}
	}

	private boolean canFly() {
		Point king1Location = ((ChineseChessGame) getGame()).GetKingLocation(0);
		Point king2Location = ((ChineseChessGame) getGame()).GetKingLocation(1);
		if (king1Location.x == king2Location.x) {
			for (int i = king1Location.y+1; i < king2Location.y-1; i++) {
				if (getGame().HasChess(new Point(king1Location.x, i))) {
					return false;
				}
			}
			return true;
		}
		return false;
	}
}

class ChCannonChess extends ChChess {
	protected ChCannonChess(int owner, ChineseChessGame game) {
		super(owner, game);
	}

	@Override
	public String getName() {
		if (owner == 0)
			return "砲";
		else
			return "炮";
	}

	@Override
	public Set<Point> GetValidTarget(Point source) {
		Set<Point> validTargets = new HashSet<Point>();
		addValidTarget(validTargets, nextStep(source, new Point(0, 1)),
				new Point(0, 1));
		addValidTarget(validTargets, nextStep(source, new Point(0, -1)),
				new Point(0, -1));
		addValidTarget(validTargets, nextStep(source, new Point(1, 0)),
				new Point(1, 0));
		addValidTarget(validTargets, nextStep(source, new Point(-1, 0)),
				new Point(-1, 0));
		return validTargets;
	}

	private void addValidTarget(Set<Point> targets, Point testPoint,
			Point direction) {
		//System.out.println("testing:" + testPoint);
		if (isPlaceableTarget(testPoint)) {
			if (!getGame().HasChess(testPoint)) {
				targets.add(testPoint);
				addValidTarget(targets, nextStep(testPoint, direction),
						direction);
			} else {
				addEatableTarget(targets, nextStep(testPoint, direction),
						direction);
			}
		}
		else if(getGame().InRange(testPoint))
		{
			addEatableTarget(targets, nextStep(testPoint, direction),
					direction);
		}
	}

	private void addEatableTarget(Set<Point> targets, Point testPoint,
			Point direction) {
		if (isPlaceableTarget(testPoint)) {
			if (getGame().HasChess(testPoint)) {
				targets.add(testPoint);
			} else {
				addEatableTarget(targets, nextStep(testPoint, direction),
						direction);
			}
		}
	}
}

class ChSoldierChess extends ChChess {
	protected ChSoldierChess(int owner, ChineseChessGame game) {
		super(owner, game);
	}

	@Override
	public String getName() {
		if (owner == 0)
			return "兵";
		else
			return "卒";
	}

	@Override
	public Set<Point> GetValidTarget(Point source) {
		Set<Point> validTargets = new HashSet<Point>();
		Point firstStep = nextStep(source, getDirection());
		if (isPlaceableTarget(firstStep))
			validTargets.add(firstStep);
		if (isCorssedTheRiver(source)) {
			for (Point nextDirection : orthogonalVectors(getDirection())) {
				Point secondStep = nextStep(source, nextDirection);
				if (isPlaceableTarget(secondStep)) {
					validTargets.add(secondStep);
				}
			}
		}

		return validTargets;
	}

	private Point getDirection() {
		if (owner == 0)
			return new Point(0, 1);
		else
			return new Point(0, -1);
	}

	private boolean isCorssedTheRiver(Point testPoint) {
		assert getGame().InRange(testPoint);
		if (owner == 0)
			return testPoint.y >= 5;
		else
			return testPoint.y < 5;
	}
}